﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Threading;

using BodaNetCommunications.Nodes;
using BodaNetCommunications.Utilities.ComTypes;
using BodaNetCommunications.Utilities.DataLibs;
using BodaNetCommunications.Utilities.Locks;
using BodaNetCommunications.Utilities.Results;
using BodaNetCommunications.Utilities.Validations;

namespace BodaNetCommunications.ModbusTcpComs
{
    /// <summary>
    /// Modbus协议在TCP/IP网络上的应用
    /// </summary>
    [Obsolete("测试的Modbus通讯应用功能，后续请使用BodaModbusTcpClient代替")]
    public class BodaModbusTcp : ModbusNode
    {
        #region 属性字段

        //创建通信对象
        private Socket _tcpClient;

        public Stopwatch sw = new Stopwatch();

        public CancellationTokenSource cts;

        //
        // 摘要:
        //     ModbusTCP组集合
        public List<ModbusTCPGroup> ModbusTCPGroupList = new List<ModbusTCPGroup>();

        /// <summary>
        /// 第一次连接
        /// </summary>
        public bool FirstConnect { get; set; }

        /// <summary>
        /// IP地址
        /// </summary>
        public string ServerURL { get; set; }

        /// <summary>
        /// 端口号
        /// </summary>
        public int Port { get; set; }

        /// <summary>
        /// 连接状态
        /// </summary>
        public bool IsConnected { get; set; }

        //发送超时
        public int SendTimeOut { get; set; } = 2000;

        public int ReceiveTimeOut { get; set; } = 2000;

        //从站地址
        public int SlaveAddress { get; set; } = 1;

        public int MaxCycleTimer { get; set; } = 5;

        //
        // 摘要:
        //     通信速率
        public long CommRate { get; set; }

        //字节顺序
        public EndianType DataFormat { get; set; } = EndianType.ABCD;

        //互斥锁
        private SimpleHybirdLock _interactiveLock = new SimpleHybirdLock();

        #endregion 属性字段

        #region 连接功能
        /// <summary>
        /// 通讯建立连接
        /// </summary>
        /// <param name="ip"></param>
        /// <param name="port"></param>
        /// <returns></returns>
        public BodaResult<bool> Connect(string ip, string port)
        {
            BodaResult<bool> bodaResult = new BodaResult<bool>();

            // 验证IP
            var validIp = ValidationUtility.IsValidIpAddress(ip);
            var ipParse = IPAddress.TryParse(ip, out var ipTemp);
            if (false == validIp || false == ipParse)
            {
                bodaResult.IsSuccess = false;
                bodaResult.Message = $"IP【{ip}】不符合IP的规则";
                return bodaResult;
            }

            // 验证Port
            var portParse = int.TryParse(port, out var portParseTemp);
            if (false == portParse || portParseTemp < 0 || portParseTemp > 65535)
            {
                bodaResult.IsSuccess = false;
                bodaResult.Message = $"Port【{port}】不符合Port的规则";
                return bodaResult;
            }

            _tcpClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp);
            _tcpClient.SendTimeout = SendTimeOut;
            _tcpClient.ReceiveTimeout = ReceiveTimeOut;

            IPEndPoint endPoint = new IPEndPoint(ipTemp, portParseTemp);
            try
            {
                _tcpClient.Connect(endPoint);
            }
            catch (Exception ex)
            {
                _tcpClient = null;
                bodaResult.IsSuccess = false;
                bodaResult.Message = ex.Message;
                return bodaResult;
            }

            bodaResult.IsSuccess = true;
            return bodaResult;
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        public BodaResult<bool> DisConnect()
        {
            BodaResult<bool> bodaResult = new BodaResult<bool>();
            bodaResult.IsSuccess = true;
            try
            {
                _tcpClient?.Close();
            }
            catch (Exception ex)
            {
                bodaResult.IsSuccess = false;
                bodaResult.Message = ex.Message;
            }
            finally
            {
                _tcpClient = null;
            }
            return bodaResult;
        }
        #endregion 连接功能

        #region 读取输出线圈 功能码0x01
        public byte[] ReadOutputStatus(int iAddress, int iLength)
        {
            //第一步：拼接报文

            ByteArray SendCommand = new ByteArray();

            // 协议/事务标识符 + 字节长度 + 单元标识符（从站地址） + 功能码
            SendCommand.Add(new byte[] { 0, 0, 0, 0, 0, 6, (byte)SlaveAddress, 1 });

            // 起始线圈地址（高位 地位）
            SendCommand.Add(new byte[] { (byte)(iAddress / 256), (byte)(iAddress % 256) });

            // 线圈数量
            SendCommand.Add(new byte[] { (byte)(iLength / 256), (byte)(iLength % 256) });

            //第二步：发送报文

            //第三步：接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            //第四步：验证报文
            int length;
            if (iLength % 8 == 0)
            {
                length = iLength / 8;
            }
            else
            {
                length = iLength / 8 + 1;
            }

            if (rcv != null && rcv.Length == 9 + length)
            {
                if (rcv[7] == 0x01 && rcv[8] == length)
                {
                    // 第五步：解析报文
                    return ByteArrayHelper.GetByteArray(rcv, 9, length);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }
        #endregion 读取输出线圈 功能码0x01

        #region 读取输入线圈  功能码0x02

        public byte[] ReadInputStatus(int iAddress, int iLength)
        {
            //第一步：拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0, 0, 6, (byte)SlaveAddress, 2 });

            SendCommand.Add(new byte[] { (byte)(iAddress / 256), (byte)(iAddress % 256) });

            SendCommand.Add(new byte[] { (byte)(iLength / 256), (byte)(iLength % 256) });

            //第二步：发送报文

            //第三步：接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            //第四步：验证报文
            int length = 0;

            if (iLength % 8 == 0)
            {
                length = iLength / 8;
            }
            else
            {
                length = iLength / 8 + 1;
            }

            if (rcv != null && rcv.Length == 9 + length)
            {
                if (rcv[7] == 0x02 && rcv[8] == length)
                {
                    // 第五步：解析报文
                    return ByteArrayHelper.GetByteArray(rcv, 9, length);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }

        #endregion 读取输入线圈  功能码0x02

        #region 读取保持寄存器   功能码0x03

        public byte[] ReadKeepReg(int iAddress, int iLength)
        {
            //第一步：拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0, 0, 6, (byte)SlaveAddress, 3 });

            SendCommand.Add(new byte[] { (byte)(iAddress / 256), (byte)(iAddress % 256) });

            SendCommand.Add(new byte[] { (byte)(iLength / 256), (byte)(iLength % 256) });

            //第二步：发送报文

            //第三步：接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            //第四步：验证报文
            if (rcv != null && rcv.Length == 9 + iLength * 2)
            {
                if (rcv[7] == 0x03 && rcv[8] == iLength * 2)
                {
                    // 第五步：解析报文
                    return ByteArrayHelper.GetByteArray(rcv, 9, iLength * 2);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }

        #endregion 读取保持寄存器   功能码0x03

        #region 读取输入寄存器   功能码0x04

        public byte[] ReadInputReg(int iAddress, int iLength)
        {
            //第一步：拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0, 0, 6, (byte)SlaveAddress, 4 });

            SendCommand.Add(new byte[] { (byte)(iAddress / 256), (byte)(iAddress % 256) });

            SendCommand.Add(new byte[] { (byte)(iLength / 256), (byte)(iLength % 256) });

            //第二步：发送报文

            //第三步：接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            //第四步：验证报文
            if (rcv != null && rcv.Length == 9 + iLength * 2)
            {
                if (rcv[7] == 0x03 && rcv[8] == iLength * 2)
                {
                    // 第五步：解析报文
                    return ByteArrayHelper.GetByteArray(rcv, 9, iLength * 2);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }

        #endregion 读取输入寄存器   功能码0x04

        #region 预置单线圈   功能码0x05

        /// <summary>
        /// 强制单个线圈
        /// </summary>
        /// <param name="iAddress"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool ForceCoil(int iAddress, bool value)
        {
            //第一步：拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0, 0, 6, (byte)SlaveAddress, 5 });

            SendCommand.Add(new byte[] { (byte)(iAddress / 256), (byte)(iAddress % 256) });

            if (value)
            {
                SendCommand.Add(new byte[] { 0xFF, 0x00 });
            }
            else
            {
                SendCommand.Add(new byte[] { 0x00, 0x00 });
            }

            //第二步：发送报文

            //第三步：接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            //第四步：验证报文

            if (rcv != null)
            {
                return ByteArrayHelper.ByteArrayEquals(SendCommand.array, rcv);
            }
            else
            {
                return false;
            }
        }

        #endregion 预置单线圈   功能码0x05

        #region 预置单个寄存器   功能码0x06

        public bool PreSetSingleReg(int iAddress, short SetValue)
        {
            //第一步：拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0, 0, 6, (byte)SlaveAddress, 6 });

            SendCommand.Add(new byte[] { (byte)(iAddress / 256), (byte)(iAddress % 256) });

            SendCommand.Add(DataFormat.GetByteArrayFrom16Bit(SetValue));

            //第二步：发送报文

            //第三步：接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            if (rcv != null)
            {
                return ByteArrayHelper.ByteArrayEquals(SendCommand.array, rcv);
            }
            else
            {
                return false;
            }
        }

        public bool PreSetSingleReg(int iAddress, ushort SetValue)
        {
            //第一步：拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0, 0, 6, (byte)SlaveAddress, 6 });

            SendCommand.Add(new byte[] { (byte)(iAddress / 256), (byte)(iAddress % 256) });

            SendCommand.Add(DataFormat.GetByteArrayFrom16Bit(SetValue));

            //第二步：发送报文

            //第三步：接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            if (rcv != null)
            {
                return ByteArrayHelper.ByteArrayEquals(SendCommand.array, rcv);
            }
            else
            {
                return false;
            }
        }

        #endregion 预置单个寄存器   功能码0x06

        #region 预置多个线圈   功能码0x0F

        public bool ForceMultiCoil(int iAddress, bool[] SetValue)
        {
            byte[] iSetValue = ByteArrayHelper.BoolArrayToByteArray(SetValue);

            //第一步：拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0 });

            int byteLength = 7 + iSetValue.Length;

            //字节长度
            SendCommand.Add((byte)(byteLength / 256));

            SendCommand.Add((byte)(byteLength % 256));

            //单元标识符和功能码
            SendCommand.Add(new byte[] { (byte)SlaveAddress, 0x0F });

            //起始线圈
            SendCommand.Add((byte)(iAddress / 256));

            SendCommand.Add((byte)(iAddress % 256));

            //线圈数量

            SendCommand.Add((byte)(SetValue.Length / 256));

            SendCommand.Add((byte)(SetValue.Length % 256));

            //字节计数
            SendCommand.Add((byte)iSetValue.Length);

            //具体字节
            SendCommand.Add(iSetValue);

            //第二步：发送报文

            //第三步：接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            if (rcv != null)
            {
                byte[] send = ByteArrayHelper.GetByteArray(SendCommand.array, 0, 12);
                send[4] = 0x00;
                send[5] = 0x06;

                return ByteArrayHelper.ByteArrayEquals(send, rcv);
            }
            else
            {
                return false;
            }
        }

        #endregion 预置多个线圈   功能码0x0F

        #region 预置多个寄存器

        //float   Int32  Uint32  float[]  Int32[]  UInt32[]         A  float  B int32  C  Uint32

        //字节数组

        public bool PreSetMultiByteArray(int iAddress, byte[] SetValue)
        {
            //首先判断一下字节数组是否正确

            if (SetValue == null || SetValue.Length == 0 || SetValue.Length % 2 == 1)
            {
                return false;
            }

            int RegLength = SetValue.Length / 2;

            //第一步：拼接报文

            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { 0, 0, 0, 0 });

            int byteLength = 7 + SetValue.Length;

            //字节长度
            SendCommand.Add((byte)(byteLength / 256));

            SendCommand.Add((byte)(byteLength % 256));

            //单元标识符和功能码
            SendCommand.Add(new byte[] { (byte)SlaveAddress, 0x10 });

            //起始寄存器
            SendCommand.Add((byte)(iAddress / 256));

            SendCommand.Add((byte)(iAddress % 256));

            //寄存器数量

            SendCommand.Add((byte)(RegLength / 256));

            SendCommand.Add((byte)(RegLength % 256));

            //字节计数
            SendCommand.Add((byte)SetValue.Length);

            //具体字节
            SendCommand.Add(SetValue);

            //第二步：发送报文

            //第三步：接收报文
            byte[] rcv = SendAndReceive(SendCommand.array);

            if (rcv != null)
            {
                byte[] send = ByteArrayHelper.GetByteArray(SendCommand.array, 0, 12);
                send[4] = 0x00;
                send[5] = 0x06;
                return ByteArrayHelper.ByteArrayEquals(send, rcv);
            }
            else
            {
                return false;
            }
        }

        public bool PreSetMultiReg(int iAddress, float SetValue)
        {
            return PreSetMultiByteArray(iAddress, DataFormat.GetByteArrayFrom32Bit(SetValue));
        }

        public bool PreSetMultiReg(int iAddress, int SetValue)
        {
            return PreSetMultiByteArray(iAddress, DataFormat.GetByteArrayFrom32Bit(SetValue));
        }

        public bool PreSetMultiReg(int iAddress, uint SetValue)
        {
            return PreSetMultiByteArray(iAddress, DataFormat.GetByteArrayFrom32Bit(SetValue));
        }

        /// <summary>
        /// 写入浮点型数组
        /// </summary>
        /// <param name="iAddress"></param>
        /// <param name="SetValue"></param>
        /// <returns></returns>
        public bool PreSetMultiReg(int iAddress, float[] SetValue)
        {
            ByteArray bSetValue = new ByteArray();
            foreach (var item in SetValue)
            {
                bSetValue.Add(DataFormat.GetByteArrayFrom32Bit(item));
            }
            return PreSetMultiByteArray(iAddress, bSetValue.array);
        }

        /// <summary>
        /// 写入int数组
        /// </summary>
        /// <param name="iAddress"></param>
        /// <param name="SetValue"></param>
        /// <returns></returns>
        public bool PreSetMultiReg(int iAddress, int[] SetValue)
        {
            ByteArray bSetValue = new ByteArray();
            foreach (var item in SetValue)
            {
                bSetValue.Add(DataFormat.GetByteArrayFrom32Bit(item));
            }
            return PreSetMultiByteArray(iAddress, bSetValue.array);
        }

        /// <summary>
        /// 写入uint数组
        /// </summary>
        /// <param name="iAddress"></param>
        /// <param name="SetValue"></param>
        /// <returns></returns>
        public bool PreSetMultiReg(int iAddress, uint[] SetValue)
        {
            ByteArray bSetValue = new ByteArray();
            foreach (var item in SetValue)
            {
                bSetValue.Add(DataFormat.GetByteArrayFrom32Bit(item));
            }
            return PreSetMultiByteArray(iAddress, bSetValue.array);
        }

        // short  ushort  short[]  ushort[]

        public bool PreSetMultiReg(int iAddress, short SetValue)
        {
            return PreSetMultiByteArray(iAddress, DataFormat.GetByteArrayFrom16Bit(SetValue));
        }

        public bool PreSetMultiReg(int iAddress, ushort SetValue)
        {
            return PreSetMultiByteArray(iAddress, DataFormat.GetByteArrayFrom16Bit(SetValue));
        }

        public bool PreSetMultiReg(int iAddress, short[] SetValue)
        {
            ByteArray bSetValue = new ByteArray();
            foreach (var item in SetValue)
            {
                bSetValue.Add(DataFormat.GetByteArrayFrom16Bit(item));
            }
            return PreSetMultiByteArray(iAddress, bSetValue.array);
        }

        public bool PreSetMultiReg(int iAddress, ushort[] SetValue)
        {
            ByteArray bSetValue = new ByteArray();
            foreach (var item in SetValue)
            {
                bSetValue.Add(DataFormat.GetByteArrayFrom16Bit(item));
            }
            return PreSetMultiByteArray(iAddress, bSetValue.array);
        }

        #endregion 预置多个寄存器

        #region 读取并接受

        /// <summary>
        /// 发送并接受: TODO 如果调用的次数太多，会导致内存泄露
        /// </summary>
        /// <param name="SendByte"></param>
        /// <returns></returns>
        private byte[] SendAndReceive(byte[] SendByte)
        {
            _interactiveLock.Enter();
            try
            {
                _tcpClient.Send(SendByte);
                var res = ReadMessage();
                return res;
            }
            catch (Exception)
            {
                return null;
            }
            finally
            {
                _interactiveLock.Leave();
            }
        }

        /// <summary>
        /// 读取缓冲区值
        /// </summary>
        /// <returns></returns>
        private byte[] ReadMessage()
        {
            int count = _tcpClient.Available;
            int cycletimer = 0;
            while (count == 0)
            {
                count = _tcpClient.Available;
                cycletimer++;
                Thread.Sleep(20);
                if (cycletimer > MaxCycleTimer)
                {
                    break;
                }
            }
            if (count == 0)
            {
                return null;
            }
            else
            {
                byte[] buffer = new byte[count];
                _tcpClient.Receive(buffer, count, SocketFlags.None);
                return buffer;
            }
        }
        #endregion 读取并接受

        //
        // 摘要:
        //     获取用于在数据表信息中显示的键值数据对信息
        //
        // 返回结果:
        //     键值数据对列表
        public override List<BodaNodeClassRenderItem> GetNodeClassRenders()
        {
            List<BodaNodeClassRenderItem> nodeClassRenders = base.GetNodeClassRenders();
            nodeClassRenders.Add(new BodaNodeClassRenderItem("IP地址", ServerURL));
            nodeClassRenders.Add(new BodaNodeClassRenderItem("端口号", Port.ToString()));
            nodeClassRenders.Add(new BodaNodeClassRenderItem("数据格式", DataFormat.ToString()));
            nodeClassRenders.Add(new BodaNodeClassRenderItem("激活情况", IsActive ? "已激活" : "未激活"));
            nodeClassRenders.Add(new BodaNodeClassRenderItem("连接情况", IsConnected ? "已连接" : "未连接"));
            if (IsConnected)
            {
                nodeClassRenders.Add(new BodaNodeClassRenderItem("通信速率", CommRate.ToString()));
            }

            return nodeClassRenders;
        }
    }
}